package jcu.salt.io;

import java.io.ByteArrayInputStream;
import java.io.IOException;

import jcu.salt.Configuration;
import jcu.salt.Logger;
import jcu.salt.client.DataHandler;

import com.rbnb.sapi.ChannelMap;
import com.rbnb.sapi.SAPIException;
import com.rbnb.sapi.Sink;

/**
 * Provides convenient access to a ring buffer for receiving data as byte array.
 * 
 * @author SAL-T team
 */
public class DataTurbineReadConnector extends DataTurbineConnector {

	private String sinkName;
	private Sink sink = null;
	private boolean read;

	public DataTurbineReadConnector(String host, String source, String sink,
			String channel) {
		super(host, source, channel);
		setSink(sink);
		init(null);
	}

	public DataTurbineReadConnector(String host, String sink, ChannelMap cmap) {
		super(host, null, null);
		setSink(sink);
		init(cmap);
	}

	private void init(ChannelMap cmap) {

		try {

			Logger.log("Starting connection to Turbine...");
			sink = new Sink();
			sink.OpenRBNBConnection(getHost(), getSink() + "_" + getSource()
					+ "_" + getChannel());

			Logger.log("connection established");

			Logger.log("try to subscribe to channel " + getSource() + "/"
					+ getChannel());
			// The channel map defines what we're looking for in the turbine
			getChannelMap().Add(getSource() + "/" + getChannel());

			// Subscribe means that we want every sample sent, as opposed to
			// Monitor
			sink.Subscribe(getChannelMap());

			Logger.log("subscribed to channel : " + getSource() + "/"
					+ getChannel());

			setConnected(true);
		} catch (SAPIException se) {
			System.err.println("Error on Turbine - not connected");
			setConnected(false);
			return;
		}
	}

	public void read(int timeout, DataHandler handler) throws IOException {

		String channels = "";
		for (String str : getChannelMap().GetChannelList()) {
			channels += "\n\t" + str;
		}
		Logger
				.log("try to read data from "
						+ getChannelMap().NumberOfChannels() + " channels :"
						+ channels);

		try {

			setRead(true);
			while (isRead()) {

				// wait for fetching data
				ChannelMap outmap = sink.Fetch(timeout);

				for (String str : outmap.GetChannelList()) {
					Logger.log("fetched something from channel" + str);

					// Look up channel index
					int chanIdx = outmap.GetIndex(str);

					// Note that we have to know how to parse the data type!
					String mime = outmap.GetMime(chanIdx);
					Logger.log("mimetype = " + mime);

					byte[][] data = outmap.GetDataAsByteArray(chanIdx);
					
					Transmittable received = null;
					Configuration config = Configuration.getInstance();
					ByteArrayInputStream bin = new ByteArrayInputStream(data[0]);
					TransmissionInputStream in = new TransmissionInputStream(
							bin, config.isSet(Configuration.ENCODED));
					try {
						received = in.readTransmitable();
						in.close();
					} catch (IOException e) {
						e.printStackTrace();
					}

					handler.handleData(received); 
				}
			}
		} catch (SAPIException mse) {
			Logger.log("Error reading data!");
		}
	}

	public byte[] read(int timeout) throws IOException {

		String channels = "";
		for (String str : getChannelMap().GetChannelList()) {
			channels += "\n\t" + str;
		}
		Logger
				.log("try to read data from "
						+ getChannelMap().NumberOfChannels() + " channels :"
						+ channels);

		byte[] result = null;
		try {

			setRead(true);
			while (isRead()) {

				// wait for fetching data
				ChannelMap outmap = sink.Fetch(timeout);

				// Logger.log("fetched something from " + outmap.toString());

				for (String str : outmap.GetChannelList()) {
					System.out.println("CHANNEL:" + str);

					// Look up channel index
					int chanIdx = outmap.GetIndex(str);

					// Note that we have to know how to parse the data type!
					String mime = outmap.GetMime(chanIdx);
					Logger.log("mimetype = " + mime);

					byte[][] data = outmap.GetDataAsByteArray(chanIdx);

					result = data[0]; // we are only interested in the first
				}
			}
		} catch (SAPIException mse) {
			Logger.log("Error reading data!");
		}
		return result;
	}

	public void close() throws IOException {

		if (isConnected() && sink != null) {

			Logger.log("Closing RBNB connection");
			sink.CloseRBNBConnection();
			sink = null;
			setConnected(false);

		}
	}

	private void setSink(String name) {
		this.sinkName = name;
	}

	private String getSink() {
		return sinkName;
	}

	public void setRead(boolean read) {
		this.read = read;
	}

	private boolean isRead() {
		return read;
	}
}
